package org.goobs.csp;

import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class CSPVariable <Domain> {

	private static long uniqueNum = 0;
	
	//Generic variables
	private long index;
	private Set <Domain> domain;
	private Object data;
	@SuppressWarnings("unchecked")
	private Collection <ICSPArc> inArcs = new HashSet <ICSPArc> ();
	@SuppressWarnings("unchecked")
	private Collection <ICSPArc> outArcs = new HashSet <ICSPArc> ();
	
	//State variables (to be cloned)
	/**
	 * Null if no value is assigned yet. Otherwise, represents the value of the
	 * variable in the current state of solving.
	 */
	private Domain curVal;
	/**
	 * A set of all values which are actually possible for this variable to take on.
	 */
	protected Collection <Domain> acceptableValues;
	
	private boolean valueChosen = false;
	
	
	//--------------------
	// Constructors
	//--------------------
	public CSPVariable (Set <Domain> domain){
		this(domain, null);
	}
	
	public CSPVariable (Set <Domain> domain, Object data){
		this.domain = domain;
		this.index = uniqueNum++;
		this.data = data;
	}
	
	private CSPVariable(){
	}
	
	
	
	//--------------------
	// State Functions
	//--------------------
	
	public Set <Domain> getDomain(){
		return domain;
	}
	
	public Object getData() {
		return data;
	}

	public void setData(Object data) {
		this.data = data;
	}
	
	@SuppressWarnings("unchecked")
	public Iterator<ICSPArc> inboundArcs() {
		return inArcs.iterator();
	}
	
	@SuppressWarnings("unchecked")
	public Iterator<ICSPArc> outboundArcs() {
		return outArcs.iterator();
	}

	@SuppressWarnings("unchecked")
	public void addArc(ICSPArc newArc) {
		CSPVariable tail = newArc.getTail();
		CSPVariable head = newArc.getHead();
		if(this == tail && !outArcs.contains(newArc)){
			outArcs.add(newArc);
		}else if(this == head && !inArcs.contains(newArc)){
			inArcs.add(newArc);
		}else{
			throw new IllegalArgumentException("adding arc to variable which is not an endpoint of arc, or an arc that already exists");
		}
	}

	@SuppressWarnings("unchecked")
	public boolean removeArc(ICSPArc newArc) {
		CSPVariable tail = newArc.getTail();
		CSPVariable head = newArc.getHead();
		if(this == tail){
			return outArcs.remove(newArc);
		}else if(this == head){
			return inArcs.remove(newArc);
		}else{
			throw new IllegalArgumentException("removing arc from variable which is not an endpoint of arc");
		}
	}

	public int totalArcCount() {
		return inArcs.size() + outArcs.size();
	}
	
	
	
	//--------------------
	// Algorithm Functions
	//--------------------

	public Domain getValue() {
		return curVal;
	}

	@SuppressWarnings("unchecked")
	public void setValue(Object newVal) {
		this.curVal = (Domain) newVal;
	}
	
	@SuppressWarnings("unchecked")
	public void chooseValue(Object newVal){
		this.curVal = (Domain) newVal;
		this.valueChosen = true;
	}
	
	@SuppressWarnings("unchecked")
	public void unchooseValue(Object oldVal){
		this.curVal = (Domain) oldVal;
		this.valueChosen = false;
	}
	
	public void reset() {
		//(reset acceptable values)
		acceptableValues = new HashSet <Domain> ();
		Iterator <Domain> iter = domain.iterator();
		while(iter.hasNext()){
			acceptableValues.add(iter.next());
		}
		//(reset value)
		curVal = null;
	}
	
	public CSPVariable <Domain> cloneValue() {
		CSPVariable <Domain> rtn = new CSPVariable <Domain> ();
		rtn.index = this.index;
		rtn.domain = this.domain;
		rtn.data = this.data;
		rtn.inArcs = this.inArcs;
		rtn.outArcs = this.outArcs;
		
		
		rtn.syncTo(this);
		
		return rtn;
	}
	
	@SuppressWarnings("unchecked")
	public void syncTo(CSPVariable otherVar){
		this.curVal = (Domain) otherVar.curVal;
		this.acceptableValues = new HashSet <Domain> ();
		Iterator <Domain> iter = otherVar.acceptableValues();
		while(iter.hasNext()){
			this.acceptableValues.add(iter.next());
		}
		this.valueChosen = otherVar.valueChosen;
	}
	
	public int numAcceptableValues(){
		if(valueChosen){
			return 1;
		}else{
			return acceptableValues.size();
		}
	}
	
	public void addAcceptableValue(Domain val){
		acceptableValues.add(val);
	}
	
	public Iterator <Domain> acceptableValues(){
		if(valueChosen){
			HashSet <Domain> set = new HashSet <Domain> ();
			set.add(curVal);
			return set.iterator();
		}else{
			return acceptableValues.iterator();
		}
	}
	
	public boolean isConsistent(){
		return curVal == null || acceptableValues.contains(curVal);
	}
	
	public int uniqueNum(){
		return (int) index;
	}
	
	
	
	//--------------------
	// Object Inherited Functions
	//--------------------
	
	@Override
	@SuppressWarnings("unchecked")
	public boolean equals(Object other){
		if(other instanceof CSPVariable){
			CSPVariable var = (CSPVariable) other;
			return this.index == var.index;
		}else if(other instanceof Integer){
			return this.uniqueNum() == (Integer) other;
		}else{
			return false;
		}
	}
	
	@Override
	public int hashCode(){
		return (int) index;
	}
	
	@Override
	public String toString(){
		String rtn = "" + this.index + ": " + this.curVal + " (data=" + this.data + ")";
		return rtn;
	}

}
